package com.hyronjs.jiangbiao.antrus.spring.beans.factory.config;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import com.hyronjs.jiangbiao.antrus.spring.core.JdkVersion;

public class DependencyDescriptor {

	private static final Method fieldAnnotationsMethod =
			ClassUtils.getMethodIfAvailable(Field.class, "getAnnotations", new Class[0]);


	private MethodParameter methodParameter;

	private Field field;

	private final boolean required;

	private final boolean eager;

	private Object[] fieldAnnotations;


	/**
	 * Create a new descriptor for a method or constructor parameter.
	 * Considers the dependency as 'eager'.
	 * @param methodParameter the MethodParameter to wrap
	 * @param required whether the dependency is required
	 */
	public DependencyDescriptor(MethodParameter methodParameter, boolean required) {
		this(methodParameter, required, true);
	}

	/**
	 * Create a new descriptor for a method or constructor parameter.
	 * @param methodParameter the MethodParameter to wrap
	 * @param required whether the dependency is required
	 * @param eager whether this dependency is 'eager' in the sense of
	 * eagerly resolving potential target beans for type matching
	 */
	public DependencyDescriptor(MethodParameter methodParameter, boolean required, boolean eager) {
		Assert.notNull(methodParameter, "MethodParameter must not be null");
		this.methodParameter = methodParameter;
		this.required = required;
		this.eager = eager;
	}

	/**
	 * Create a new descriptor for a field.
	 * Considers the dependency as 'eager'.
	 * @param field the field to wrap
	 * @param required whether the dependency is required
	 */
	public DependencyDescriptor(Field field, boolean required) {
		this(field, required, true);
	}

	/**
	 * Create a new descriptor for a field.
	 * @param field the field to wrap
	 * @param required whether the dependency is required
	 * @param eager whether this dependency is 'eager' in the sense of
	 * eagerly resolving potential target beans for type matching
	 */
	public DependencyDescriptor(Field field, boolean required, boolean eager) {
		Assert.notNull(field, "Field must not be null");
		this.field = field;
		this.required = required;
		this.eager = eager;
	}


	/**
	 * Return the wrapped MethodParameter, if any.
	 * <p>Note: Either MethodParameter or Field is available.
	 * @return the MethodParameter, or <code>null</code> if none
	 */
	public MethodParameter getMethodParameter() {
		return this.methodParameter;
	}

	/**
	 * Return the wrapped Field, if any.
	 * <p>Note: Either MethodParameter or Field is available.
	 * @return the Field, or <code>null</code> if none
	 */
	public Field getField() {
		return this.field;
	}

	/**
	 * Return whether this dependency is required.
	 */
	public boolean isRequired() {
		return this.required;
	}

	/**
	 * Return whether this dependency is 'eager' in the sense of
	 * eagerly resolving potential target beans for type matching.
	 */
	public boolean isEager() {
		return this.eager;
	}


	/**
	 * Determine the declared (non-generic) type of the wrapped parameter/field.
	 * @return the declared type (never <code>null</code>)
	 */
	public Class getDependencyType() {
		return (this.field != null ? this.field.getType() : this.methodParameter.getParameterType());
	}

	/**
	 * Determine the generic element type of the wrapped Collection parameter/field, if any.
	 * @return the generic type, or <code>null</code> if none
	 */
	public Class getCollectionType() {
		if (JdkVersion.getMajorJavaVersion() < JdkVersion.JAVA_15) {
			return null;
		}
		return (this.field != null ?
				GenericCollectionTypeResolver.getCollectionFieldType(this.field) :
				GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter));
	}

	/**
	 * Determine the generic key type of the wrapped Map parameter/field, if any.
	 * @return the generic type, or <code>null</code> if none
	 */
	public Class getMapKeyType() {
		if (JdkVersion.getMajorJavaVersion() < JdkVersion.JAVA_15) {
			return null;
		}
		return (this.field != null ?
				GenericCollectionTypeResolver.getMapKeyFieldType(this.field) :
				GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter));
	}

	/**
	 * Determine the generic value type of the wrapped Map parameter/field, if any.
	 * @return the generic type, or <code>null</code> if none
	 */
	public Class getMapValueType() {
		if (JdkVersion.getMajorJavaVersion() < JdkVersion.JAVA_15) {
			return null;
		}
		return (this.field != null ?
				GenericCollectionTypeResolver.getMapValueFieldType(this.field) :
				GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter));
	}

	/**
	 * Obtain the annotations associated with the wrapped parameter/field, if any.
	 * @return the parameter/field annotations, or <code>null</code> if there is
	 * no annotation support (on JDK < 1.5). The return value is an Object array
	 * instead of an Annotation array simply for compatibility with older JDKs;
	 * feel free to cast it to <code>Annotation[]</code> on JDK 1.5 or higher.
	 */
	public Object[] getAnnotations() {
		if (this.field != null) {
			if (this.fieldAnnotations != null) {
				return this.fieldAnnotations;
			}
			if (fieldAnnotationsMethod == null) {
				return null;
			}
			this.fieldAnnotations = (Object[]) ReflectionUtils.invokeMethod(fieldAnnotationsMethod, this.field);
			return this.fieldAnnotations;
		}
		else {
			return this.methodParameter.getParameterAnnotations();
		}
	}

}